# coding: utf-8

#item based CF

from __future__ import division  
import numpy as np  
import scipy as sp
import pandas as pd
import scipy.spatial.distance as ssd
from sklearn.metrics import accuracy_score
from sklearn.metrics import recall_score

class  Item_based_CF_Song:  
    def __init__(self, X):  
        self.X = X  #评分表
        self.mu = np.mean(self.X[:,0])  #average rating

        self.ItemsForUser={}   #用户打过分的所有Item
        self.UsersForItem={}   #给Item打过分的所有用户
        
        for i in range(self.X.shape[0]):  
            uid=self.X[i][1]  #user id
            i_id=self.X[i][2] #item_id 
            rat=self.X[i][0]  #rating
            
            self.UsersForItem.setdefault(i_id,{})  
            self.ItemsForUser.setdefault(uid,{}) 
            
            self.UsersForItem[i_id][uid]=rat  
            self.ItemsForUser[uid][i_id]=rat
            
            #self.similarity.setdefault(i_id,{}) 
            
        pass  
    
        n_Items = len(self.UsersForItem)+1 #数组的索引从0开始，浪费第0个元素
        #print n_Items-1
        self.similarity = np.zeros((n_Items, n_Items), dtype=np.float)
        self.similarity[:,:] = -1
           
    
    #计算Item i_id1和i_id2之间的相似性
    def sim_cal(self, i_id1, i_id2):
        if self.similarity[i_id1][i_id2]!=-1:  #如果已经计算好
            return self.similarity[i_id1][i_id2]  
        
        si={}  
        for user in self.UsersForItem[i_id1]:  #所有对Item1打过分的的user
            if user in self.UsersForItem[i_id2]:  #如果该用户对Item2也打过分
                #print self.UsersForItem[i_id2]
                si[user]=1  #user为一个有效用用户
        
        #print si
        n=len(si)   #有效用户数，有效用户为即对Item1打过分，也对Item2打过分
        if (n==0):  #没有共同打过分的用户，相似度设为1.因为最低打分为1？
            self.similarity[i_id1][i_id2]=0  
            self.similarity[i_id1][i_id1]=0  
            return 0  
        
        #所有有效用户对Item1的打分
        s1=np.array([self.UsersForItem[i_id1][u] for u in si])  
        
        #所有有效用户对Item2的打分
        s2=np.array([self.UsersForItem[i_id2][u] for u in si]) 
        
        sum1=np.sum(s1)  
        sum2=np.sum(s2)  
        sum1Sq=np.sum(s1**2)  
        sum2Sq=np.sum(s2**2)  
        pSum=np.sum(s1*s2)  
        
        #分子
        num=pSum-(sum1*sum2/n)  
        
        #分母
        den=np.sqrt((sum1Sq-sum1**2/n)*(sum2Sq-sum2**2/n))  
        if den==0: 
            self.similarity[i_id1][i_id2]=0  
            self.similarity[i_id2][i_id1]=0  
            return 0  
        
        self.similarity[i_id1][i_id2]=1-ssd.correlation(s1,s2)  
        self.similarity[i_id2][i_id1]=1-ssd.correlation(s1,s2) 
        
  
        #sum1=np.sum(s1) 
        #s1_avg=np.sum(s1)/n
        #sum1Sq=np.sum(s1**2)
       
        #sum2=np.sum(s2) 
        #s2_avg=np.sum(s2)/n
        #sum2Sq=np.sum(s2**2)   
        
          
        
        #分子
        #num=pSum-(sum1*sum2/n)  
        
        #分母
        #den=np.sqrt((sum1Sq-2*s1_avg*sum1+n*(s1_avg**2))*(sum2Sq-2*s2_avg*sum2+n*(s2_avg**2))  
        #if den==0:
           #self.similarity[i_id1][i_id2]=0  
           #self.similarity[i_id2][i_id1]=0  
           #return 0 
                    
        #self.similarity[i_id1][i_id2]=(1-ssd.correlation(s1,s2)) 
        #self.similarity[i_id2][i_id1]=(1-ssd.correlation(s1,s2)) 
        return (1-ssd.correlation(s1,s2)) 
            
    #预测用户uid对Item i_id的打分
    def pred(self,uid,i_id):  
        sim_accumulate=0.0  
        rat_acc=0.0  
        
                 
        
        #if(i_id == 599):    
            #print self.UsersForItem[i_id]
        #i=1
        #for item_temp in self.ItemsForUser[uid]:  #用户uid打过分的所有Item
            #temp+=self.ItemsForUser[uid][item_temp]
            #rat_uid_avg=temp/i #计算用户uid打过分的所有item的平均分
            #i+=1
            
                    
                    
        for item in self.ItemsForUser[uid]:  #用户uid打过分的所有Item
            sim = self.sim_cal(item,i_id)    #该Item与i_id之间的相似度
            if sim<0:continue  
            #print sim,self.user_movie[uid][item],sim*self.user_movie[uid][item]  
            
            rat_acc += sim * self.ItemsForUser[uid][item]
            sim_accumulate += sim  
        
        #print rat_acc,sim_accumulate  
        if sim_accumulate==0: #no same user rated,return average rates of the data  
            return  self.mu  
        return rat_acc/sim_accumulate  
    
    #测试
    def test(self,test_X):  
        test_X=np.array(test_X) 
        output=[]  
        accuray_all=[]
        recall_all=[]
        sums=0  
        print ("the test data size is ",test_X.shape)
        print('xxx')
        for i in range(test_X.shape[0]):  
            uid = test_X[i][1]  #user id
            i_id = test_X[i][2] #item_id 
        
            #设置默认值，否则用户或item没在训练集中出现时会报错
            self.UsersForItem.setdefault(i_id,{})  
            self.ItemsForUser.setdefault(uid,{})
            
            pre=self.pred(uid, i_id)  
            output.append(pre)  
            #print pre,test_X[i][2]  
            #sums += (pre-test_X[i][2])**2  
        #rmse=np.sqrt(sums/test_X.shape[0])  
        #print "the rmse on test data is ",rmse
        test_X=pd.DataFrame(test_X, columns=['Score','Userid','Songid'],dtype=np.int)
        test_X['pre_Score']=output
        test_X.to_csv('Test_X_itemCF.csv',header=True)
        for userid  in set(test_X['Userid']): 
             testdata_uid = test_X.loc[(test_X["Userid"] ==userid)]
                    
             testdata_uid_ori = testdata_uid.sort_values(by='Score', ascending=False)
             temp1=np.linspace(1,len(testdata_uid_ori.Score),len(testdata_uid_ori.Score),dtype='int')
             testdata_uid_ori.index=np.array(temp1)
             testdata_uid_ori.loc[(testdata_uid_ori.index <=20) ,"Top20"] = 1
             testdata_uid_ori=testdata_uid_ori.fillna(0)
             testdata_uid_ori['Top20'] = testdata_uid_ori['Top20'].astype('int64')
             testdata_uid_ori = testdata_uid_ori.sort_values(by='Songid', ascending=False)  
                    
                    
                    
             testdata_uid_pre = testdata_uid.sort_values(by='pre_Score', ascending=False)     
             temp2=np.linspace(1,len(testdata_uid_pre.pre_Score),len(testdata_uid_pre.pre_Score),dtype='int')
             testdata_uid_pre.index=np.array(temp2)                    
             testdata_uid_pre.loc[(testdata_uid_pre.index <=20) ,"Top20"] = 1
             testdata_uid_pre=testdata_uid_pre.fillna(0)
             testdata_uid_pre['Top20'] = testdata_uid_pre['Top20'].astype('int64')                    
             testdata_uid_pre = testdata_uid_pre.sort_values(by='Songid', ascending=False)   
                    
             
             accuracy = accuracy_score(testdata_uid_ori.Top20, testdata_uid_pre.Top20)
             recall = recall_score(testdata_uid_ori.Top20, testdata_uid_pre.Top20)
             accuray_all.append(accuracy)
             recall_all.append(recall)
        print ("accuray is ",sum(accuray_all)/len(accuray_all))          
        print ("recall is ",sum(recall_all)/len(recall_all)) 
        
        
        return sum(accuray_all)/len(accuray_all), sum(recall_all)/len(recall_all)  